package cn.ygyg.bps.bus.starter;

import cn.ygyg.bps.bus.starter.listener.BpsTraceListener;
import cn.ygyg.bps.bus.starter.repository.BpsBusEventTraceRepository;
import cn.ygyg.bps.bus.starter.repository.BpsBusEventTraceRepositoryImpl;
import cn.ygyg.bps.bus.starter.service.EventTraceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration;
import org.springframework.cloud.bus.*;
import org.springframework.cloud.bus.endpoint.EnvironmentBusEndpoint;
import org.springframework.cloud.bus.event.*;
import org.springframework.cloud.context.environment.EnvironmentManager;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.BindingServiceConfiguration;
import org.springframework.cloud.stream.config.BindingServiceProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

/**
 * @desc：BpsBusAutoConfiguration
 * @author: guanliang.xue
 * @date: 2021/5/11 09:58
 * @version:
 * @since:
 */
@Configuration
@ConditionalOnBusEnabled
@EnableBinding(SpringCloudBusClient.class)
@EnableConfigurationProperties(BusProperties.class)
@AutoConfigureBefore(BindingServiceConfiguration.class)
@AutoConfigureAfter(LifecycleMvcEndpointAutoConfiguration.class)
public class BpsBusAutoConfiguration implements ApplicationEventPublisherAware {

    private static final Logger logger = LoggerFactory.getLogger(BpsBusAutoConfiguration.class);

    /**
     * Name of the Bus path matcher.
     */
    public static final String BUS_PATH_MATCHER_NAME = "busPathMatcher";

    /**
     * Name of the Spring Cloud Config property.
     */
    public static final String CLOUD_CONFIG_NAME_PROPERTY = "spring.cloud.config.name";

    private final ServiceMatcher serviceMatcher;

    private final BindingServiceProperties bindings;

    private final BusProperties bus;

    private MessageChannel cloudBusOutboundChannel;

    private ApplicationEventPublisher applicationEventPublisher;


    public BpsBusAutoConfiguration(ServiceMatcher serviceMatcher, BindingServiceProperties bindings, BusProperties bus) {
        this.serviceMatcher = serviceMatcher;
        this.bindings = bindings;
        this.bus = bus;
    }

    @PostConstruct
    public void init() {
        BindingProperties inputBinding = this.bindings.getBindings()
                .get(SpringCloudBusClient.INPUT);
        if (inputBinding == null) {
            this.bindings.getBindings().put(SpringCloudBusClient.INPUT,
                    new BindingProperties());
        }
        BindingProperties input = this.bindings.getBindings()
                .get(SpringCloudBusClient.INPUT);
        if (input.getDestination() == null
                || input.getDestination().equals(SpringCloudBusClient.INPUT)) {
            input.setDestination(this.bus.getDestination());
        }
        BindingProperties outputBinding = this.bindings.getBindings()
                .get(SpringCloudBusClient.OUTPUT);
        if (outputBinding == null) {
            this.bindings.getBindings().put(SpringCloudBusClient.OUTPUT,
                    new BindingProperties());
        }
        BindingProperties output = this.bindings.getBindings()
                .get(SpringCloudBusClient.OUTPUT);
        if (output.getDestination() == null
                || output.getDestination().equals(SpringCloudBusClient.OUTPUT)) {
            output.setDestination(this.bus.getDestination());
        }
    }

    @Override
    public void setApplicationEventPublisher(
            ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Autowired
    @Output(SpringCloudBusClient.OUTPUT)
    public void setCloudBusOutboundChannel(MessageChannel cloudBusOutboundChannel) {
        this.cloudBusOutboundChannel = cloudBusOutboundChannel;
    }

    @EventListener(classes = RemoteApplicationEvent.class)
    public void acceptLocal(RemoteApplicationEvent event) {
        if (this.serviceMatcher.isFromSelf(event)
                && !(event instanceof AckRemoteApplicationEvent)) {
            this.cloudBusOutboundChannel.send(MessageBuilder.withPayload(event).build());
            // fixme 增加发送记录

        }
    }

    @StreamListener(SpringCloudBusClient.INPUT)
    public void acceptRemote(RemoteApplicationEvent event) {
        logger.info("acceptRemote.event {}",event.toString());
        if (event instanceof AckRemoteApplicationEvent) {
            if (this.bus.getTrace().isEnabled() && !this.serviceMatcher.isFromSelf(event)
                    && this.applicationEventPublisher != null) {
                this.applicationEventPublisher.publishEvent(event);
            }
            // If it's an ACK we are finished processing at this point
            return;
        }
        if (this.serviceMatcher.isForSelf(event)
                && this.applicationEventPublisher != null) {
            if (!this.serviceMatcher.isFromSelf(event)) {
                this.applicationEventPublisher.publishEvent(event);
            }
            if (this.bus.getAck().isEnabled()) {
                AckRemoteApplicationEvent ack = new AckRemoteApplicationEvent(this,
                        this.serviceMatcher.getServiceId(),
                        this.bus.getAck().getDestinationService(),
                        event.getDestinationService(), event.getId(), event.getClass());
                this.cloudBusOutboundChannel
                        .send(MessageBuilder.withPayload(ack).build());
                this.applicationEventPublisher.publishEvent(ack);
            }
        }
        if (this.bus.getTrace().isEnabled() && this.applicationEventPublisher != null) {
            // We are set to register sent events so publish it for local consumption,
            // irrespective of the origin
            this.applicationEventPublisher.publishEvent(new SentApplicationEvent(this,
                    event.getOriginService(), event.getDestinationService(),
                    event.getId(), event.getClass()));
        }
    }
    
    @Bean
    public BpsBusEventTraceRepository getBpsBusEventTraceRepository(){
        logger.info("加载 BpsBusEventTraceRepository >>>>>>");
        return new BpsBusEventTraceRepositoryImpl();
    }

    @Configuration
    protected static class MatcherConfiguration {

        @BusPathMatcher
        // There is a @Bean of type PathMatcher coming from Spring MVC
        @ConditionalOnMissingBean(name = BusAutoConfiguration.BUS_PATH_MATCHER_NAME)
        @Bean(name = BusAutoConfiguration.BUS_PATH_MATCHER_NAME)
        public PathMatcher busPathMatcher() {
            return new DefaultBusPathMatcher(new AntPathMatcher(":"));
        }

        @Resource
        private BusProperties properties;

        @Bean
        public ServiceMatcher serviceMatcher(@BusPathMatcher PathMatcher pathMatcher, Environment environment) {
            String[] configNames = environment.getProperty(CLOUD_CONFIG_NAME_PROPERTY,
                    String[].class, new String[] {});
            ServiceMatcher serviceMatcher = new ServiceMatcher(pathMatcher,properties.getId(), configNames);
            return serviceMatcher;
        }

    }

    @Configuration
    @ConditionalOnClass(BpsBusEventTraceRepository.class)
    @ConditionalOnProperty(value = "spring.cloud.bus.trace.enabled")
    protected static class BusAckTraceConfiguration {

        /*@Resource
        private EventTraceService eventTraceService;*/

        @Bean
        @ConditionalOnMissingBean(BpsTraceListener.class)
        public BpsTraceListener ackTraceListener(BpsBusEventTraceRepository repository) {
            logger.info("加载 BpsTraceListener >>>>>>");
            return new BpsTraceListener(repository,null);
        }

    }

    @Configuration
    @ConditionalOnClass(EnvironmentManager.class)
    @ConditionalOnBean(EnvironmentManager.class)
    protected static class BusEnvironmentConfiguration {
        @Bean
        @ConditionalOnProperty(value = "spring.cloud.bus.env.enabled", matchIfMissing = true)
        public EnvironmentChangeListener environmentChangeListener() {
            return new EnvironmentChangeListener();
        }

        @Configuration
        protected static class EnvironmentBusEndpointConfiguration {

            @Resource
            private BusProperties bus;

            @Bean
            public EnvironmentBusEndpoint environmentBusEndpoint(ApplicationContext context) {
                return new EnvironmentBusEndpoint(context, bus.getId());
            }
        }
    }
}
